This document is Copyright (c) 1996 by Sylvie Gallet. I encourage you to copy and distribute it, so long as you leave it unchanged. It may NOT be used for commercial purposes without my explicit prior permission.
Any comments, questions, corrections... are welcome. My addresses are:
CompuServe: 101324,3444 Internet : 101324.3444@compuserve.com
Pseudo-HiColor (PHC) was co-discovered by Jim Deutch who wrote the very first PHC formula and Lee Skinner who realized the enormous possibilities that this technique offers. Given the great interest aroused by PHC, the Fractint developers decided to implement three new variables that make formulas much simpler; a big thank you to Tim Wegner, Jonathan Osuch and George Martin for doing this! Pseudo-TrueColor (PTC) is an extension of the PHC concept. All the PHC and PTC formulas included in this document are by the author of these lines. Thanks to Lee Skinner and George Martin for their help, suggestions and encouragements.
The text-only version of this tutorial is also available for downloading
It is possible to combine two fractals (say fractal_0 and fractal_1) in one image. Imagine that alternate screen pixels form a checkerboard pattern (represented by 0's and 1's) as follows:
0 1 0 1 0 . . 1 0 1 0 1 . . 0 1 0 1 0 . . 1 0 1 0 1 . . . . . . . . .
If one fractal is drawn on the "white squares" (the 1's) and the other on the black squares (the 0's), the separate fractals will be visible, and at higher screen resolutions you will not be able to see the way the individual pixels intermesh with the others. The effect is as if the two fractals were drawn on separate transparent sheets and overlaid.
...
+
=
Fractint v. 19.5 provides a predefined variable "whitesq", which is automatically set to 1 prior to the calculation of a white square pixel, and to 0 prior to calculation of a black square pixel. Let's see how to use this variable in a formula.
Suppose that fractal_0 and fractal_1 have the following assignment statements:
fractal_0 { fractal_1 { ; whitesq == 0 ; whitesq == 1 ... ... var = something var = somethingelse ... ... } }
To assign the appropriate value to var, our PHC formula will have to simulate an IF...THEN...ELSE... construct:
if (whitesq == 0) /* if "whitesq == 0" is TRUE */ var = something ; else /* if "whitesq == 0" is FALSE */ /* or "whitesq == 1" is TRUE */ var = somethingelse ;This can be done with the following line:
var = something * (whitesq == 0) + somethingelse * (whitesq == 1)
which results in "something" being assigned to var for the black squares, and "somethingelse" being assigned to var for the white squares.
To understand how it works, it's important to know that Fractint represents TRUE with a one, and FALSE with a zero, therefore:
* if whitesq is equal to 0: - "whitesq == 0" evaluates to 1 - "whitesq == 1" evaluates to 0 and var = something * 1 + somethingelse * 0 = something * if whitesq is equal to 1: - "whitesq == 0" evaluates to 0 - "whitesq == 1" evaluates to 1 and var = something * 0 + somethingelse * 1 = somethingelse
You may notice that the result of the test "whitesq == 1" is equal to the value of the variable "whitesq", so we can use the following statement:
var = something * (whitesq == 0) + somethingelse * whitesq
Suppose that fractal_0 and fractal_1 use the bailout tests bailout_0 and bailout_1. What will be the PHC bailout test?
Remember that if the answer of a bailout test is "TRUE" (or 1), the loop must be performed again; otherwise, it is time to quit iterating.
The bailout test of the PHC formula must be the translation in the parser language of the following structure:
if (whitesq == 0) /* "whitesq == 0" TRUE */ { if (bailout_0 == 1) /* bailout_0 TRUE */ PHC_bailout = 1 ; else /* bailout_0 FALSE */ PHC_bailout = 0 ; } else /* "whitesq == 1" TRUE */ { if (bailout_1 == 1) /* bailout_1 TRUE */ PHC_bailout = 1 ; else /* bailout_1 FALSE */ PHC_bailout = 0 ; }
We can see that PHC_bailout is TRUE only in two cases:
when (whitesq == 0) and (bailout_0 == 1) or when (whitesq == 1) and (bailout_1 == 1)
We finally get the following expression where "&&" and "||" are the logical operators "and" and "or":
(bailout_0 && (whitesq == 0)) || (bailout_1 && whitesq)
and since comparisons have precedence over logical operators :
(bailout_0 && whitesq == 0) || (bailout_1 && whitesq)
This is the easiest case: both fractal use the same iteration instruction and the same bailout test.
mandel { ; Mandel set of z^2 + c z = c = pixel : z = z*z + c |z| <= 4 } julia { ; Julia set of z^2 + (-0.75,0.1234) z = pixel , c = (-0.75,0.1234) : z = z*z + c |z| <= 4 }
Since the only difference is the initial value of c, the PHC formula will use whitesq only in the init section:
phc_mj { ; overlay the Mandel set of z^2 + c with ; the Julia set of z^2 + (-0.75,0.1234) z = pixel c = pixel * (whitesq == 0) + (-0.75,0.1234) * whitesq : z = z*z + c |z| <= 4 }
and the Julia set will be drawn on the white squares of the checkerboard, and the Mandelbrot set on the black squares.
Here, except "z = pixel", everything is different.
mandel { ; Mandel set of z^2 + c z = c = pixel : z = z*z + c |z| <= 4 }
newton { ; Julia set of Newton's method ; applied to z^3 - 1 = 0 z = pixel : n = z^3 - 1 , d = 3*z^2 z = z - n/d |n| >= 0.000001 }
The resulting PHC formula is:
phc_mn_A { ; overlay the Mandel set of z^2 + c with the Julia ; set of Newton's method applied to z^3 - 1 = 0 z = c = pixel : n = z^3 - 1 , d = 3*z^2 z = (z*z + c) * (whitesq == 0) + (z - n/d) * whitesq (|z| <= 4 && whitesq == 0) || (|n| >= 0.000001 && whitesq) }
You'll notice that c is initialized to pixel even when whitesq == 1 but it is not used by the Newton algorithm.
Overlaying three fractals can be done with the following pattern:
0 1 2 0 1 2 . . 1 2 0 1 2 0 . . 2 0 1 2 0 1 . . 0 1 2 0 1 2 . . . . . . . . . .
Fractint v. 19.5 provides a predefined variable "scrnpix", which is set to (column, row) prior to calculation of each pixel. The upper left hand corner of the screen is (0,0); at resolution 1024x768, the lower right hand corner is therefore (1023,767).
Here, we'll use scrnpix to assign the value 0, 1 or 2 to a variable r (as you can see, I choose a very explicit name!).
With col = real(scrnpix) and row = imag(scrnpix), the value of r should be:
r = (col + row) modulo 3or, using the parser language:
cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / 3)
But this instruction doesn't work (see "what's wrong").
The following instruction does work:
r = cr - 3 * trunc(cr / real(3))
Now, let's see an example:
Suppose you want to overlay the three following fractals:
mandel { ; Mandel set of z^2 + c z = c = pixel : z = z*z + c |z| <= 4 }
julia { ; Julia set of z^2 + (-0.75,0.1234) z = pixel , c = (-0.75,0.1234) : z = z*z + c |z| <= 4 }
newton { ; Julia set of Newton's method ; applied to z^3 - 1 = 0 z = pixel : n = z^3 - 1 , d = 3*z^2 z = z - n/d |n| >= 0.000001 }
We can merge them in the following way:
ptc_mjn_A { ; overlay the Mandel set of z^2 + c with the Julia set ; of z^2 + (-0.75,0.1234) and the Julia set of Newton's ; method applied to z^3 - 1 = 0 cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / real(3)) z = pixel c = pixel * (r == 0) + (-0.75,0.1234) * (r == 1) : n = z^3 - 1 , d = 3*z^2 z = (z*z + c) * (r == 0 || r == 1) + (z - n/d) * (r == 2) (|z| <= 4 && (r == 0 || r == 1)) || (|n| >= 0.000001 && r == 2) }
In this formula, we could consider three cases (r == 0, r == 1 and r == 2) but since the first two ones use the same "z = ..." statement and the same bailout test, we can combine them in "r == 0 || r == 1" or "r != 2" (r not equal to 2). Thus, the last lines become:
z = (z*z + c) * (r != 2) + (z - n/d) * (r == 2) (|z| <= 4 && r != 2) || (|n| >= 0.000001 && r == 2)
The best dithering is produced by the following pattern:
0 1 2 3 0 1 . . 2 3 0 1 2 3 . . 0 1 2 3 0 1 . . 2 3 0 1 2 3 . . . . . . . . . . and r is given by the formula: r = (col + 2*row) modulo 4 or, using the parser language: cr = real(scrnpix) + 2 * imag(scrnpix) r = cr - 4 * trunc(cr / 4)
and r can then be used as in the previous examples, to combine four fractals in one image.
Here is an example:
mand_0 { mand_1 { z = c = sin(pixel) : z = c = pixel : z = z*z + c z = z*z + c |real(z)| <= 4 |z| <= 4 } } mand_2 { mand_3 { z = c = 1/pixel : z = c = -pixel : z = z*z + c z = z*z + c |imag(z)| < 4 |real(z)+imag(z)| < 4 } } phc_4m_A { ; overlay four Mandels with different initializations ; and bailout tests ; Isn't it terrific??? cr = real(scrnpix) + 2 * imag(scrnpix) r = cr - 4 * trunc(cr / 4) c0 = sin(pixel) * (r == 0) c1 = pixel * (r == 1) c2 = 1/pixel * (r == 2) c3 = -pixel * (r == 3) z = c = c0 + c1 + c2 + c3 : z = z*z + c test_0 = |real(z)| < 4 && r == 0 test_1 = |z| < 4 && r == 1 test_2 = |imag(z)| < 4 && r == 2 test_3 = |real(z)+imag(z)| < 4 && r == 3 test_0 || test_1 || test_2 || test_3 }
Let's look at this formula in detail.
The best way to make your formulas run a little faster would be to read or reread Bradley Beacham's FRMTUTOR.TXT (that'll save me having to plagiarize his work <g>).
In the newton formula, we can replace
n = z^3 - 1 , d = 3*z^2with
z2 = z*z , n = z2*z - 1 , d = 3*z2This gives:
newton_B { ; Julia set of Newton's method ; applied to z^3 - 1 = 0 z = pixel : z2 = z*z , n = z2*z - 1 , d = 3*z2 z = z - n/d |n| >= 0.000001 }
Let's make the same change in phc_mn_B and ptc_mjn_B but here, we don't need to recalculate z*z: we'll replace it with z2.
phc_mn_B { ; overlay the Mandel set of z^2 + c with the Julia ; set of Newton's method applied to z^3 - 1 = 0 z = c = pixel : z2 = z*z , n = z2*z - 1 , d = 3*z2 z = (z2 + c) * (whitesq == 0) + (z - n/d) * whitesq (|z| < 4 && whitesq == 0) || (|n| >= 0.000001 && whitesq) } ptc_mjn_B { ; overlay the Mandel set of z^2 + c with the Julia set ; of z^2 + (-0.75,0.1234) and the Julia set of Newton's ; method applied to z^3 - 1 = 0 cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / real(3)) z = pixel c = pixel * (r == 0) + (-0.75,0.1234) * (r == 1) : z2 = z*z , n = z2*z - 1 , d = 3*z2 z = (z2 + c) * (r != 2) + (z - n/d) * (r == 2) (|z| < 4 && r != 2) || (|n| >= 0.000001 && r == 2) }
Consider the initialization section of phc_4m_A:
c0 = sin(pixel) * (r == 0) c1 = pixel * (r == 1) c2 = 1/pixel * (r == 2) c3 = -pixel * (r == 3) z = c = c0 + c1 + c2 + c3 : You may notice that c1 + c3 = pixel * (r == 1) - pixel * (r == 3) = pixel * ((r == 1) - (r == 3)) Then: z = c = c0 + c2 + pixel * ((r == 1) - (r == 3))
This will make the formula a little more obscure but avoids a multiplication.
phc_4m_B { ; overlay four Mandels with different ;initializations and bailout tests ; Isn't it terrific??? cr = real(scrnpix) + 2*imag(scrnpix) r = cr - 4 * trunc(cr / 4) c0 = sin(pixel) * (r == 0) c2 = 1/pixel * (r == 2) z = c = c0 + c2 + pixel * ((r == 1) - (r == 3)) : z = z*z + c test_0 = |real(z)| < 4 && r == 0 test_1 = |z| < 4 && r == 1 test_2 = |imag(z)| < 4 && r == 2 test_3 = |real(z)+imag(z)| < 4 && r == 3 test_0 || test_1 || test_2 || test_3 }
As we've seen earlier, the PHC or PTC dithering is based on the value of a variable initialized by Fractint (whitesq) or in the formula (r).
To write a PHC formula, you just have to use "whitesq" at least once. A PTC formula will start with these lines:
cr = real(scrnpix) + imag(scrnpix) r = cr - 3 * trunc(cr / real(3)) or these ones: cr = real(scrnpix) + 2 * imag(scrnpix) r = cr - 4 * trunc(cr / 4)
For a good dithering, it's essential to leave the variables "whitesq", "cr" and "r" intact.
For example, if you change:
r = cr - 3 * trunc(cr / real(3)) to: r = cr - 3 * trunc(cr / real(3.5)) the values of r will be: 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 4, ...
and the result will be quite disappointing. Also, it will be helpful if you use the templates (including the variable names "r" and "cr") in exactly the form shown above. This will make it easier to identify and update formulas using PHC and PTC if future changes to Fractint's formula parser would make such updating desirable.
Lee Skinner has already updated more than 70 PHC formulas previously posted and uploaded on Compuserve's Graphdev forum. The revised formulas, for Fractint 19.5 and later only, are available in the file phc.frm, downloadable from Graphdev lib 4 as phc_frms.zip. However, you'll probably need to convert your old formulas that have never been uploaded on Graphdev. This is very easy! If the formula contains these lines:
count = (count + 1) * (count != (p3-1)) evenodd = (evenodd == (count == 1)) oddeven = (evenodd == 0) or these ones: countreset = (count < 1024) count = count * countreset evenodd=(evenodd < 1)*(count != 0)*countreset + (countreset == 0)*evenodd oddeven = (evenodd < 1) count = count + 1
just delete them and replace evenodd by whitesq and oddeven by (whitesq==0).
Fractint provides two calculation modes: integer math and floating point math.
Though it often produces very nice images, integer math has an important limitation: you can't use numbers greater than 255.999... In integer mode, when you *think* you're using say 372, Fractint use 116 (372-256=116). For this reason, the Fractint developers decided to force the floating point mode when the formula parser detects the use of one of the predefined variables "scrnpix", "scrnmax" and "maxit".
When you run a formula in floating point mode, the formula parser uses some tricks to optimize the code. Trunc(cr / 3) is the victim of one of these tricks.
The problem is this: trunc(cr/3) is replaced by trunc(cr*(1/3)), but since 1/3 is represented internally as .33333..., cr*(1/3) is slightly less than cr/3. If cr is a multiple of 3, trunc(cr/3) therefore returns one less than you would expect. For example trunc(6/3) calculates as trunc(6*.3333333...) or trunc(1.9999...), which in turn evaluates to 1 instead of the expected result of 2.
Replacing 3 with real(3) circumvents this problem and yields the desired result.
Precedence is a way to make mathematical expressions more readable by using less parentheses. For a better comprehension of this section, you might want to look at the table of precedence in the Fractint documentation.
The following expressions are mathematically equivalent because division and multiplication have a higher precedence than addition and subtraction (this means that divisions and multiplications will be performed before additions and subtraction):
2+(3*((5*3)+(4/5))*(7-5)) 2 + 3 * (5*3 + 4/5) * (7 - 5)
(they both evaluate to 96.8) I must confess that I tend to prefer the second one... (OK, I'm cheating, I just added a few white spaces... <g>).
Now, let's see another example:
In this expression:
(|z| < 4) && (whitesq == 0)
the parentheses mean that the comparisons must be performed before the logical AND. Comparisons have a higher precedence than logical operators thus, we can remove the parentheses:
|z| < 4 && whitesq == 0
We'll end with the bailout test of phc_mn_A:
(|z| < 4 && whitesq == 0) || (|n| >= 0.000001 && whitesq)
Can we remove the parentheses? The answer is: no! Without parentheses,
and since we know that comparisons are performed first, this expression has
the following format:
A && B || C && D
"&&" and "||" have the same precedence and in such a case, the expression
must be calculated from the left to the right. Just look at this:
(1 && 1) || (1 && 0) = 1 || 0 = 1 1 && 1 || 1 && 0 = 1 || 1 && 0 = 1 && 0 = 0
With the new variables introduced in Fractint 19.5, PHC and PTC formulas are now resolution independent and the image can be interrupted, saved and restored. Panning an even number of pixels for PHC images or multiples of 3 for "24-bit PTC's" and multiples of 4 for "32-bit PTC's is possible without artifacts.
All PHC and PTC formulas require passes=1.
The use of symmetry in PHC or PTC formulas or par files is not recommended since symmetry alters the pattern along the axes and results in horizontal or vertical lines.
That's all for now! I hope you found this text interesting and useful. I'm planning an update of Bradley Beacham's Formula Tutorial which could include this text and any subject you'd like to see treated.
Back to
The Fractint Home Page.
or back to
The Fractint Index Page.